multi property是一個容易被忽略的功能,可以視為以EdgeDBSet為容器來收集各種primitive。
考慮schema如下:
type User {
multi sports: str;
}
User object中有一個multi sports property,為str型別,用來記錄其喜愛的運動。
接著我們新增一個User object,並將{"baseball", "swimming"}這個EdgeDBSet指定給multi sports property。
insert User {
sports:= {"baseball", "swimming"}
};
假設我們想將badminton加入multi sports property中,可以這麼寫:
update User
set {
sports+= {"badminton"}
};
上面這個query利用+=來新增badminton,是一個快速將元素加入EdgeDBSet的寫法,可以想成in place地加入元素。
如果不習慣這樣的寫法,也可以寫成:
update User
set {
sports:= .sports union {"badminton"}
};
留意這邊使用的是:=。:=代表將.sports union {"badminton"}這個set operation的結果指定給multi sports property。
假設我們想將badminton自multi sports property移除,可以這麼寫:
update User
set {
sports-= {"badminton"}
};
或是寫成:
update User
set {
sports:= .sports except {"badminton"}
};
留意這邊使用的是:=。:=代表將.sports except {"badminton"}這個set operation的結果指定給multi sports property。
array是有序的collection type,與EdgeDBSet一樣,其內所含的object須為同一型別,且可以被索引(索引值由0開始)。例如:
select [1, 2, 3][0];
{1}
請注意,此query返回結果依然是EdgeDBSet。
除了利用索引取得單個元素外,也可以使用切片,例如:
select [1, 2, 3][1:];
{[2, 3]}
切片邏輯與Python相似。舉例來說,當select array[from:to]:
考慮schema如下:
type User {
sports: array<str>;
}
接著我們新增一個User object,並將["baseball", "swimming"]這個array指定給sports property。
insert User {
sports:= ["baseball", "swimming"]
};
假設我們想將badminton加入sports property中,可以這麼寫:
update User
set {
sports:= .sports ++ ["badminton"]
};
請留意這邊我們需要使用:=及++。EdgeDB在設計上,將array視為immutable,所以我們其實無法修改array,只能夠於每次需要更新array時,重新建立一個,然後指定給原先的property。
假設我們想將badminton自sports property移除,可以這麼寫:
update User
set {
sports:= (
with idx:= find(.sports, "badminton")
select .sports[:idx] ++ .sports[idx+1:]
)
};
這邊我們利用find先找出badminton所在的索引值,接著再手動重新建立一個排除badminton的array,指定給sports property。
由於找不到元素時,find會回傳-1,上面的query將因此產生錯誤的答案。此時,我們可以透過if-else來幫忙,當想要移除的元素不在array中時,我們只是將原先的array再一次指定回sports property。
update User
set {
sports:= (
with idx:= find(.sports, "badminton")
select .sports[:idx] ++ .sports[idx+1:] if idx != -1 else .sports
)
};
如果覺得上面邏輯有點亂的話,可以試著在with區塊中透過contains預先建立一個判斷邏輯,來檢視array中是否有該元素:
update User
set {
sports:= (
with is_in:= contains(.sports, "badminton"),
idx:= find(.sports, "swimming")
select .sports[:idx] ++ .sports[idx+1:] if is_in else .sports
)
};
multi property vs array至於何時該選擇multi property而何時又該選擇array呢?根據David MacLeody在Easy EdgeDB第八章所建議的:
multi property;反之則使用array。constraint或針對其使用index on時,使用multi property。array。由於使用array需要於每次update重新生成一個array,操作起來比較麻煩。所以我個人會傾向將array使用在元素順序重要,且更新時只需不斷使用++將新元素添加到array的情況。
EdgeDBSet與array的互相轉換array_unpack()array_unpack()可以將array轉變為EdgeDBSet:
select array_unpack([2, 3, 5]);
{3, 2, 5}
這邊需留意由array轉變為EdgeDBSet時,順序有可能會打亂(記得EdgeDBSet為無序的嗎?)。
array_agg()array_agg()可以將EdgeDBSet轉變為array:
select array_agg({2, 3, 5});
{[2, 3, 5]}
這邊需留意由EdgeDBSet轉變為array時,有兩種情況可以將順序將會保留在array中:
EdgeDBSet為顯性寫出來的型態,如{2, 3, 5}。order by時。舉例來說,考慮schema如下:
type User {
required name: str;
}
接著執行下面query,生成三個User object:
insert User {name:= "Jeff"};
insert User {name:= "Tom"};
insert User {name:="Cathy"};
此時下面query因為使用了order by,所以array內元素將會依照字母順序排好:
select array_agg(User.name order by User.name);
{['Cathy', 'Jeff', 'Tom']}